/**
 * @file x509_cert_create.c
 * @brief X.509 certificate generation
 *
 * @section License
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * Copyright (C) 2010-2025 Oryx Embedded SARL. All rights reserved.
 *
 * This file is part of CycloneCRYPTO Open.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * @author Oryx Embedded SARL (www.oryx-embedded.com)
 * @version 2.5.2
 **/

//Switch to the appropriate trace level
#define TRACE_LEVEL CRYPTO_TRACE_LEVEL

//Dependencies
#include "core/crypto.h"
#include "pkix/x509_cert_create.h"
#include "pkix/x509_cert_format.h"
#include "pkix/x509_sign_format.h"
#include "encoding/asn1.h"
#include "debug.h"

//Check crypto library configuration
#if (X509_SUPPORT == ENABLED)


/**
 * @brief Generate a X.509 certificate
 * @param[in] prngAlgo PRNG algorithm
 * @param[in] prngContext Pointer to the PRNG context
 * @param[in] certReqInfo Certificate request information
 * @param[in] subjectPublicKey Subject's public key (optional parameter)
 * @param[in] issuerCertInfo Issuer's certificate (optional parameter)
 * @param[in] serialNumber Serial number (optional parameter)
 * @param[in] validity Validity period
 * @param[in] signatureAlgo Signature algorithm
 * @param[in] signerPrivateKey Pointer to the issuer's private key
 * @param[out] output Buffer where to store the X.509 certificate
 * @param[out] written Length of the resulting X.509 certificate
 * @return Error code
 **/

error_t x509CreateCertificate(const PrngAlgo *prngAlgo, void *prngContext,
   const X509CertRequestInfo *certReqInfo, const void *subjectPublicKey,
   const X509CertInfo *issuerCertInfo, const X509SerialNumber *serialNumber,
   const X509Validity *validity, const X509SignAlgoId *signatureAlgo,
   const void *signerPrivateKey, uint8_t *output, size_t *written)
{
   error_t error;
   size_t n;
   size_t length;
   uint8_t *p;
   const X509Name *issuer;
   const X509SubjectPublicKeyInfo *publicKeyInfo;
   X509OctetString tbsCert;
   X509AuthKeyId authKeyId;
   Asn1Tag tag;

   //Check parameters
   if(certReqInfo == NULL || validity == NULL || signatureAlgo == NULL ||
      signerPrivateKey == NULL || written == NULL)
   {
      return ERROR_INVALID_PARAMETER;
   }

   //Point to the buffer where to write the certificate
   p = output;
   //Length of the certificate
   length = 0;

   //Self-signed certificate?
   if(issuerCertInfo == NULL)
   {
      //Self-issued certificates are CA certificates in which the issuer
      //and subject are the same entity
      issuer = &certReqInfo->subject;

      //Where a CA distributes its public key in the form of a self-signed
      //certificate, the authority key identifier may be omitted
      authKeyId = certReqInfo->attributes.extensionReq.authKeyId;

      //Point to the subject's public key
      publicKeyInfo = &certReqInfo->subjectPublicKeyInfo;
   }
   else
   {
      //Check issuer's certificate version
      if(issuerCertInfo->tbsCert.version >= X509_VERSION_3)
      {
         const X509Extensions *extensions;

         //Point to the extensions of the issuer's certificate
         extensions = &issuerCertInfo->tbsCert.extensions;

         //Make sure the cA boolean is asserted
         if(!extensions->basicConstraints.cA)
            return ERROR_BAD_CERTIFICATE;

         //Check if the KeyUsage extension is present
         if(extensions->keyUsage.bitmap != 0)
         {
            //Make sure the keyCertSign is asserted
            if((extensions->keyUsage.bitmap & X509_KEY_USAGE_KEY_CERT_SIGN) == 0)
               return ERROR_BAD_CERTIFICATE;
         }
      }

      //The issuer field identifies the entity that has signed and issued
      //the certificate
      issuer = &issuerCertInfo->tbsCert.subject;

      //The KeyIdentifier field of the AuthorityKeyIdentifier extension must
      //be included in all certificates generated by conforming CAs to
      //facilitate certification path construction
      authKeyId.critical = FALSE;
      authKeyId.keyId.value = issuerCertInfo->tbsCert.extensions.subjectKeyId.value;
      authKeyId.keyId.length = issuerCertInfo->tbsCert.extensions.subjectKeyId.length;

      //Point to the issuer's public key
      publicKeyInfo = &issuerCertInfo->tbsCert.subjectPublicKeyInfo;
   }

   //Format TBSCertificate structure
   error = x509FormatTbsCertificate(prngAlgo, prngContext, serialNumber,
      signatureAlgo, issuer, validity, &certReqInfo->subject,
      &certReqInfo->subjectPublicKeyInfo, subjectPublicKey,
      &certReqInfo->attributes.extensionReq, &authKeyId, p, &n);
   //Any error to report?
   if(error)
      return error;

   //The ASN.1 DER-encoded TBSCertificate is used as the input to the
   //signature function
   tbsCert.value = p;
   tbsCert.length = n;

   //Advance data pointer
   ASN1_INC_POINTER(p, n);
   length += n;

   //Format SignatureAlgorithm structure
   error = x509FormatSignatureAlgo(signatureAlgo, p, &n);
   //Any error to report?
   if(error)
      return error;

   //Advance data pointer
   ASN1_INC_POINTER(p, n);
   length += n;

   //Format SignatureValue structure
   error = x509FormatSignatureValue(prngAlgo, prngContext, &tbsCert,
      signatureAlgo, publicKeyInfo, signerPrivateKey, p, &n);
   //Any error to report?
   if(error)
      return error;

   //Advance data pointer
   ASN1_INC_POINTER(p, n);
   length += n;

   //The certificate is encapsulated within a sequence
   tag.constructed = TRUE;
   tag.objClass = ASN1_CLASS_UNIVERSAL;
   tag.objType = ASN1_TYPE_SEQUENCE;
   tag.length = length;

   //Write the corresponding ASN.1 tag
   error = asn1InsertHeader(&tag, output, &n);
   //Any error to report?
   if(error)
      return error;

   //Total number of bytes that have been written
   *written = tag.totalLength;

   //Successful processing
   return NO_ERROR;
}

#endif
